package com.ac.service;

import java.util.ArrayList;
import java.util.List;

import org.bson.Document;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.ac.exception.RRException;
import com.ac.model.AuthApi;
import com.ac.model.AuthMenu;
import com.ac.model.AuthToken;
import com.ac.model.AuthTokenList;
import com.ac.model.AuthUser;
import com.ac.response.Res;
import com.ac.util.AuthConf;
import com.ac.util.HeaderUtils;
import com.ac.util.StrUtils;

import cn.hutool.core.util.IdUtil;
import lombok.AllArgsConstructor;

@Service
@AllArgsConstructor
public class AuthTokenService {

	private final AuthApiService ApiService;

	private final AuthMenuService MenuService;

	final MongoTemplate mongo;

	/**
	 * 为用户创建token并返回菜单列表和token
	 * 
	 * @param user
	 * @param userAgent
	 * @param ip
	 * @param isPhone
	 * @return
	 */
	@Transactional(rollbackFor = Exception.class)
	public Document createToken(AuthUser user, Document map) {

		Document res = new Document();

		String userAgent = map.get("userAgent") + "";
		String password = map.get("password") + "";
		String ip = map.get("ip") + "";
		boolean isPhone = map.getBoolean("isPhone");

		long thisTime = System.currentTimeMillis();
		String token = IdUtil.objectId();
		String devType = HeaderUtils.getUserAgent(userAgent);
		AuthToken tokenInfo = mongo.findOne(new Query(Criteria.where("userId").is(user.getId())), AuthToken.class);
		long endTime = thisTime + AuthConf.outtime;
		List<AuthTokenList> tokenList = new ArrayList<AuthTokenList>();

		if (tokenInfo != null) {
			tokenList = tokenInfo.getTokenList();
			if (!isPhone) {
				// 登录失败后失败次数加1，一定时间内直接返回登录失败
				int errNum = tokenInfo.getErrNum();
				long preErrTime = tokenInfo.getPreErrTime();
				if (errNum > AuthConf.loginerrnum - 1) {
					if (thisTime - preErrTime < AuthConf.loginouttime)
						throw new RRException("账号或密码错误");
				}
			}
		}

		if (!isPhone) {
			String userpw = user.getPassword()+"";
			if (!userpw.equals(password)) {
				if (tokenInfo != null) {
					int errNum = tokenInfo.getErrNum() + 1;
					if (errNum < AuthConf.loginerrnum + 1) {
						Criteria ct = Criteria.where("id").is(tokenInfo.getId());
						Update update1 = new Update();
						update1.set("errNum", errNum).set("preErrTime", thisTime);
						mongo.updateFirst(Query.query(ct), update1, AuthToken.class);
					}
				}
				throw new RRException("账号或密码错误");
			}
		}

		List<AuthMenu> menuList = MenuService.menuList(user);
		if (menuList.size() < 1)
			throw new RRException("登录失败，无效角色");
		List<AuthApi> apiListT = ApiService.apiList(user);
		List<String> apiList = new ArrayList<String>();
		for (AuthApi authApi : apiListT) {
			apiList.add(authApi.getUrl());
		}

		if (tokenInfo == null) {
			AuthToken ti = new AuthToken();
			ti.insert();
			ti.setUserId(user.getId());
			ti.setPreTime(System.currentTimeMillis());

			AuthTokenList tokenitem = new AuthTokenList();
			tokenitem.insert();

			tokenitem.setDevType(devType);
			tokenitem.setToken(token);
			tokenitem.setEndTime(endTime);
			tokenitem.setPreToken("");
			tokenitem.setPreEndTime(endTime);
			tokenitem.setIp(ip);

			tokenList.add(tokenitem);

			ti.setTokenList(tokenList);

			ti.setApiList(apiList);

			mongo.insert(ti);
		} else {
			boolean isHave = false;
			int index = 0;
			for (int i = 0; i < tokenList.size(); i++) {
				AuthTokenList tokenitemTemp = tokenList.get(i);
				String itemDevType = tokenitemTemp.getDevType();
				if (devType.equals(itemDevType)) {
					isHave = true;
					index = i;
				}
			}
			AuthTokenList tokenitem = null;
			if (isHave) {
				tokenitem = tokenList.get(index);
				tokenitem.update();
			} else {
				tokenitem = new AuthTokenList();
				tokenitem.insert();
			}

			tokenitem.setDevType(devType);
			tokenitem.setToken(token);
			tokenitem.setEndTime(endTime);
			tokenitem.setPreToken("");
			tokenitem.setPreEndTime(endTime);
			tokenitem.setIp(ip);

			if (isHave) {
				tokenList.set(index, tokenitem);
			} else {
				tokenList.add(tokenitem);
			}

			Criteria ct = Criteria.where("id").is(tokenInfo.getId());
			mongo.updateFirst(Query.query(ct),
					new Update().set("tokenList", tokenList).set("updateTime", thisTime).set("apiList", apiList),
					AuthToken.class);
		}
		res.append("token", token);
		res.append("menuList", menuList);
		res.append("apiList", apiList);
		return res;
	}

	/**
	 * 获取最新token，如果超时，更新token，返回当前token
	 */
	@Transactional(rollbackFor = Exception.class)
	public Res getToken(Res res, String token) {
		List<Criteria> qList = new ArrayList<Criteria>();
		qList.add(Criteria.where("tokenList.token").is(token));
		qList.add(Criteria.where("tokenList.preToken").is(token));
		Criteria criteria = new Criteria();
		criteria.orOperator(qList);
		Query query = new Query(criteria);
		AuthToken tokenInfo = mongo.findOne(query, AuthToken.class);
		String newToken = IdUtil.objectId();
		res.token(newToken);
		if (tokenInfo == null)
			return res;
		List<AuthTokenList> tokenList = tokenInfo.getTokenList();
		AuthTokenList tokenitem = null;
		int index = -1;
		for (int i = 0; i < tokenList.size(); i++) {
			AuthTokenList authTokenItem = tokenList.get(i);
			if (authTokenItem.getToken().equals(token)) {
				tokenitem = authTokenItem;
				index = i;
			}
			if (authTokenItem.getPreToken().equals(token)) {
				res.token(authTokenItem.getToken() + StrUtils.getRandom(0, 9));
				return res;
			}
		}
		if (tokenitem == null)
			return res;
		long thisTime = System.currentTimeMillis();
		long endTime = tokenitem.getEndTime();
		if (thisTime - endTime > 0) {// 过期

			tokenitem.setPreToken(token);
			tokenitem.setPreEndTime(thisTime + AuthConf.preouttime);
			tokenitem.setToken(newToken);
			tokenitem.setEndTime(thisTime + AuthConf.outtime);
			tokenList.set(index, tokenitem);

			Criteria ct = Criteria.where("_id").is(tokenInfo.getId());
			mongo.updateFirst(Query.query(ct), new Update().set("tokenList", tokenList).set("updateTime", thisTime),
					AuthToken.class);

			res.token(newToken + StrUtils.getRandom(0, 9));
		}

		return res;

	}

	/**
	 * 判断token是否有效或者存在
	 */
	public Document isToken(String token) {
		Document res = new Document();
		Criteria criteria = new Criteria();
		criteria.orOperator(Criteria.where("tokenList.token").is(token),
				Criteria.where("tokenList.preToken").is(token));
		AuthToken tokenInfo = mongo.findOne(Query.query(criteria), AuthToken.class);
		res.append("tokenInfo", tokenInfo);
		res.append("is", false);
		if (tokenInfo == null) {
			return res;
		}
		List<AuthTokenList> tokenList = tokenInfo.getTokenList();
		AuthTokenList tokenitem = null;
		long endTime = -1;
		int outtime = -1;
		for (int i = 0; i < tokenList.size(); i++) {
			AuthTokenList authTokenItem = tokenList.get(i);
			if (authTokenItem.getToken().equals(token)) {
				res.replace("is", true);
				return res;
			}
			if (authTokenItem.getPreToken().equals(token)) {
				tokenitem = authTokenItem;
				endTime = authTokenItem.getPreEndTime();
				outtime = AuthConf.preouttime;
			}
		}
		if (tokenitem == null)
			return res;
		long thisTime = System.currentTimeMillis();
		if (thisTime - endTime > outtime)
			return res;
		res.replace("is", true);
		return res;
	}

	/**
	 * 根据token获取用户id
	 * 
	 * @param token
	 * @return
	 */
	public String getUserId(String token) {
		List<Criteria> qList = new ArrayList<Criteria>();
		qList.add(Criteria.where("tokenList.token").is(token));
		qList.add(Criteria.where("tokenList.preToken").is(token));
		Criteria criteria = new Criteria();
		criteria.orOperator(qList);
		Query query = new Query(criteria);
		AuthToken tokenInfo = mongo.findOne(query, AuthToken.class);
		if (tokenInfo != null)
			return tokenInfo.getUserId();
		return null;
	}

	/**
	 * 判断token登录的用户是否有相关的api列表
	 */
	public boolean isApi(AuthToken tokenInfo, String url) {
		List<String> apiList = tokenInfo.getApiList();
		for (String authApi : apiList) {
			if (url.equals(authApi))
				return true;
		}
		return false;
	}

}
